function SpecSuite() {
    this.fixtureQueue = [];
    this.outputHandler = null;
}

SpecSuite.prototype = {
    FindContextsStartingWith: function(startsWith) {
        return this;
    },
    
    AddSpecs: function(fixture) {      
      for (var fixtureName in fixture) {
        this.fixtureQueue.push(new SpecFixture(fixtureName, fixture[fixtureName]));
      }
    },
    
    Execute: function() {
        this.outputHandler = new SpecOutput();
        
        for (var fixtureName in this.fixtureQueue) {
            this.fixtureQueue[fixtureName].execute(this.outputHandler);
        }
        
        this.outputHandler.renderOutput("outputDiv");
    }
}


function SpecOutput() {
    this.fixtureResults = [];
    this.currentFixture = null;
}

SpecOutput.prototype = {
    beginLoggingFixture: function(name) {        
        var result = { "name":name, "tests":[] };
        this.fixtureResults.push(result);
        this.currentFixture = result;
    },
    
    logFailedTest: function(name, error) {
        this.currentFixture.tests.push({"name":name, "message":error, passed:false});
    },
    
    logPassedTest: function(name) {
        this.currentFixture.tests.push({"name":name, passed:true});
    },
    
    renderOutput: function(targetElemId) {
        var outputElem = $("#"+targetElemId);
        var mainContainer = $("<ul class='fixture_list'></ul>");
        outputElem.append(mainContainer);
        
        var listItem;
        for (var i=0; i<this.fixtureResults.length; i++) {
            listItem = $("<li class='fixture'>" + this.fixtureResults[i].name + "</li>");
            mainContainer.append(listItem);
            this.renderFixture(listItem, this.fixtureResults[i]);
        }
    },
    
    renderFixture: function(container, fixture) {
        var testList = $("<ul class='test_list'></ul>");
        container.append(testList);
        
        var test, html;
        for (var i=0; i<fixture.tests.length; i++) {
            test = fixture.tests[i];            
            html = test.passed ?
                        "<li class='passed_test_li'><span class='passed_test_text'>" + test.name + "</span></li>" :
                        "<li class='failed_test_li'><span class='failed_test_text'>" + test.name + ": " + test.message + "</span></li>" 
            
            testList.append(html);
        }
    }
};


function SpecFixture(fixtureName, fixture) {    
    this.contextMethods = [];
    this.testMethods = [];
    
    this.fixtureName = fixtureName;
    this.fixtureObj = fixture;
    this.parseFixture(fixture);
}

SpecFixture.prototype = {
    parseFixture: function(fixture) {        
        var methodNames = {};
      
        for(var item in fixture) {            
            if (this.isContextName(item)) {
                this.contextMethods.push(item);
            }
            else if ( (typeof(fixture[item]) == 'function') && (this.isTestName(item)) ) {                
                this.testMethods.push(item);
            }
            else {
                this[item] = fixture[item];
            }
        }
    },
    
    isContextName: function(name) {
        return name.startsWith("context");
    },
    
    isTestName: function(name) {
        return name.startsWith("should");
    },
    
    execute: function(outputHandler) {
        var fixture = this.fixtureObj;
        var skipTests = false;
        
        outputHandler.beginLoggingFixture(this.fixtureName);
        
        var thisFixture = this;
        
        this.contextMethods.forEach( 
            function(elem, index, array) {                
                try {
                    fixture[elem].apply(thisFixture);
                }
                catch(e) {
                    //throw e;
                    outputHandler.logFailedContext(elem, e)
                    skipTests = true;
                }
            });
        
        
        if (skipTests)
            return;
        
        this.testMethods.forEach( 
            function(elem, index, array) {                
                try {
                    fixture[elem].apply(thisFixture);
                    outputHandler.logPassedTest(elem);
                }
                catch (e) {
                    outputHandler.logFailedTest(elem, e);
                }
            });
    }
}




String.prototype.startsWith = function(text) {
    if (text.length > this.length) return false;
    if (text.length == 0) return false;
    
    var thisSegment = this.substr(0, text.length);
    return (thisSegment === text);
}

if (!Array.prototype.forEach)
{
  Array.prototype.forEach = function(fun /*, thisp*/)
  {
    var len = this.length;
    if (typeof fun != "function")
      throw new TypeError();

    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this)
        fun.call(thisp, this[i], i, this);
    }
  };
}


function verify(obj) {
    function verifyOptions(obj) {         
        this.objToCheck = obj;
    }
    
    verifyOptions.prototype = {        
        is: function(value) {
            if (this.objToCheck.valueOf() != value) {        
                var msg = "Expected '" + this + "' to be the same as '" + value + "'";
                throw msg;
            }
        },
        
        is_not: function(value) {
            if (this.objToCheck.valueOf() == value) {
                var msg = "Expected '" + this + "' to NOT be the same as '" + value + "'";
                throw msg;
            }        
        },
        
        contains: function(value) {
            if (this.objToCheck.indexOf(value) == -1) {
                throw "Did not contain: " + value.toString();
            }    
        },
        
        does_not_contain: function(value) {
            if (this.objToCheck.indexOf(value) > -1) {
                throw "Should not contain: " + value.toString();
            }    
        }
    };
      
    return new verifyOptions(obj);          
}
